package socket;

import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;

/**
 * 聊天室服务端
 */
public class Server {
    private ServerSocket serverSocket;
    //集合中存放所有客户端输出流用于广播消息
    private List<PrintWriter> allOut = new ArrayList<>();

    public Server() {
        try {
            System.out.println("正在启动服务端...");
            /*
            创建ServerSOCKET的同时要申请服务端口，该端口不能与系统其他程序
            开启的端口一致，否则会抛出异常：
            java.io.BindException:address already in use
            解决办法：
            更换端口
             */
            serverSocket = new ServerSocket(8088);
            System.out.println("服务端建立完毕！");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    //工作方法
    public void start() {
        try {
            while (true) {
                System.out.println("等待客户端连接....");
            /*
            ServerSocket提供的一个重要方法：
            serverSocket是一个阻塞方法
             */
                Socket socket = serverSocket.accept();
                System.out.println("一个客户端连接了");
                //启动一个线程处理与该客户啊短的交互 new 一个线程任务 放到线程里
               /*
       创建CientHanlder对象时会先走带参构造方法 获取到服务端的值 socket
       传给带参参构造方法 然后把socket的值 赋值给 private Socket socket;中的socket
       就能使方法内能使用方法外的变量
                */
                CientHanlder handler = new CientHanlder(socket);
                //创建一个线程 并执行任务
                Thread t = new Thread(handler);
                //开始任务
                t.start();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        Server server = new Server();
        server.start();
    }

    /**
     * 将处理客户端交互的操作在独立的线程上进行，这里主要的工作就是与某个客户端交互
     */
    private class CientHanlder implements Runnable {
        private Socket socket;
        private String host;

        public CientHanlder(Socket socket) {
            this.socket = socket;
            //得到别的ip然后得到字符串模式
            host = socket.getInetAddress().getHostName();
        }

        @Override
        //接口中run是无参的 不能传参
        public void run() {
            PrintWriter pw = null;
            try {
                //socket获得输入流
                InputStream in = socket.getInputStream();
                InputStreamReader isr = new InputStreamReader(in, StandardCharsets.UTF_8);
                BufferedReader br = new BufferedReader(isr);

                //socket获得输出流 用于给客户端发消息
                OutputStream out = socket.getOutputStream();
                OutputStreamWriter osw = new OutputStreamWriter(out, StandardCharsets.UTF_8);
                BufferedWriter bw = new BufferedWriter(osw);
                pw = new PrintWriter(bw, true);
                //将该输出流存入共享机会allOut里
                synchronized (pw) {
                    allOut.add(pw);
                }
                System.out.println(host + "上线了！当前在线在线人数为：" + allOut.size());
                while (true) {
                    String message;
                    while ((message = br.readLine()) != null) {
                        //如果客户端正常挥手 服务端要断开连接
                        System.out.println(host + "说：" + message);
                        //将消息回复给所有客户端 使用循环遍历机会内所有的客户端地址
                        synchronized (allOut) {
                            //遍历时也要加一个锁 互斥 防止遍历的时候被删除
                            for (PrintWriter pww : allOut) {
                                pww.println(host + "说：" + message);
                            }
                        }
                    }
                }
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                //处理客户端断开后的操作
                synchronized (allOut) {
                    allOut.remove(pw);//将当前客户端输出流从共享集合中删除
                }
                System.out.println(host + "下线了，当前人数为：" + allOut.size());
                try {
                    socket.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}
